/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

#include "splashcore.h"

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Construction / destruction

BuildGraphNode::BuildGraphNode(BuildGraph* graph)
	: m_updated(false)
	, m_graph(graph)
	, m_job(NULL)
{
}

BuildGraphNode::~BuildGraphNode()
{
	ClearBuildJob();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Accessors

string BuildGraphNode::GetHashCached()
{
	if(m_cachedhash == "")
		return (m_cachedhash = GetHash());
	return m_cachedhash;
}

void BuildGraphNode::ForceUpdate(Cluster* cluster)
{
	m_updated = true;
	DebugPrintfHeading(3, "Updating %s (forcibly)\n", GetDescription().c_str());

	//Get hash for this build operation
	string hash = GetHashCached();
	DebugPrintfSilent (5, "    Hash is \"%s\"\n", hash.c_str());
	
	//Generate intermediate file names
	string outdir   = m_graph->GetMetaDir() + "/" + hash;
	string binpath  = GetBinaryPath();
	string final_binpath = GetOutputPath();
	string bindir = GetDirOfFile(final_binpath);
	
	bool symlink_needed = (final_binpath != "") && (final_binpath != binpath);
	
	//Create the output directory if it doesn't exist
	if(!DoesDirectoryExist(outdir))
		MakeDirectoryRecursive(outdir, 0755);
	if(!DoesDirectoryExist(bindir))
		MakeDirectoryRecursive(bindir, 0755);
		
	//Run the build
	if(symlink_needed)
		unlink(final_binpath.c_str());
	Update(cluster);
	if(symlink_needed)
		symlink(binpath.c_str(), final_binpath.c_str());	
}

void BuildGraphNode::UpdateCached(Cluster* cluster)
{
	if(!m_updated)
	{
		m_updated = true;
		
		DebugPrintfHeading(3, "Updating %s\n", GetDescription().c_str());
	
		//Get hash for this build operation
		string hash = GetHashCached();
		DebugPrintfSilent (5, "    Hash is \"%s\"\n", hash.c_str());
		
		//Generate intermediate file names
		string outdir   = m_graph->GetMetaDir() + "/" + hash;
		string binpath  = GetBinaryPath();
		string okpath   = outdir  + "/build_successful";
		string failpath = outdir  + "/build_failed";
		string final_binpath = GetOutputPath();
		
		bool symlink_needed = (final_binpath != "") && (final_binpath != binpath);
		
		//Recursively create the output directories if they don't exist
		MakeDirectoryRecursive(outdir.c_str(), 0755);
		MakeDirectoryRecursive(GetDirOfFile(final_binpath).c_str(), 0755);
		
		//Delete binary symlink
		if(symlink_needed)
			unlink(final_binpath.c_str());
		
		//Check if it's already been built and rebuild if required
		DebugPrintfSilent(5, "    Output directory is %s\n", outdir.c_str());
		if(DoesFileExist(okpath))
			DebugPrintfSilent (4, "    Build successful, re-using existing binary\n");
		else if(DoesFileExist(failpath))
		{			
			DebugPrintfSilent (4, "    Build failed, returning same errors as before\n");
			m_job = new FailedClusterJob;
		}
		else
		{
			DebugPrintfSilent(4, "    Rebuild is necessary\n");
			Update(cluster);
		}
		
		if(symlink_needed)
			symlink(binpath.c_str(), final_binpath.c_str());
	}
}

bool BuildGraphNode::GetOutput(vector<string>& output)
{
	string hash = GetHashCached();
	string dir = m_graph->GetMetaDir() + "/" + hash;
	char line[2048];
	if(DoesDirectoryExist(dir))
	{
		//The directory exists
		//Read output
		string fname = dir + "/output.txt";
		FILE* fp = fopen(fname.c_str(), "r");
		if(!fp)			//no stdout generated
			return false;
		
		//Read full output
		while(NULL != fgets(line, sizeof(line), fp))
		{
			//trim off newline chars
			size_t len = strlen(line);
			if( (len > 0) && (line[len-1] == '\n') )
				line[len-1] = '\0';
				
			//Special processing!
			//If line begins with three spaces followed by a non-space, line-wrap (Xilinx multiline output)
			if(isspace(line[0]) && isspace(line[1]) && isspace(line[2]) && !isspace(line[3]) && !output.empty())
			{
				string& tail = output[output.size() - 1];
				if(isspace(tail[tail.length() - 1]))
					tail += (line + 3);
				else
					tail += (line + 2);
			}
			else			
				output.push_back(line);
		}
		
		fclose(fp);
	}
	return !output.empty();
}

bool BuildGraphNode::GetFilteredOutput(std::vector<std::string>& output)
{
	//default to no filtering, but still remove filename spam
	bool rval = GetOutput(output);
	for(size_t i=0; i<output.size(); i++)
		output[i] = RemoveImplicitFilenames(output[i]);
	return rval;
}

string BuildGraphNode::RemoveImplicitFilenames(string line)
{
	//default to no filtering
	return line;
}

bool BuildGraphNode::GetFilteredOutput(vector<string>& output, const char** whitelist, size_t wsize, const char** blacklist, size_t bsize)
{
	//raw unfiltered output
	vector<string> temp;
	GetOutput(temp);

	for(size_t i=0; i<temp.size(); i++)
	{
		string str = temp[i];
		
		//Reject empty lines
		if(str.empty())
			continue;
		
		//Reject all lines that DO NOT contain a substring in the whitelist
		//Empty whitelist is considered an all-pass
		bool hit = false;
		if(whitelist != NULL)
		{
			for(size_t j=0; j<wsize; j++)
			{
				if(str.find(whitelist[j]) != str.npos)
				{
					hit = true;
					break;
				}
			}
			if(!hit)
				continue;
		}
			
		//Reject all lines that DO contain a substring in the blacklist
		hit = false;
		for(size_t j=0; j<bsize; j++)
		{
			if(str.find(blacklist[j]) != str.npos)
			{
				hit = true;
				break;
			}
		}
		if(hit)
			continue;
			
		//If we get this far it's a match
		output.push_back(RemoveImplicitFilenames(str));
	}
	
	return !output.empty();
}

void BuildGraphNode::ClearBuildJob()
{
	if(m_job)
	{
		delete m_job;
		m_job = NULL;
	}
}

void BuildGraphNode::ComputeFlattenedDependencies(unordered_set<BuildGraphNode*>& deps)
{
	for(size_t i=0; i<m_dependencies.size(); i++)
	{
		BuildGraphNode* dep = m_dependencies[i];
		if(deps.find(dep) == deps.end())
			dep->ComputeFlattenedDependencies(deps);
		deps.emplace(dep);
	}
}
